package ferp.core.ai.tree2;

import ferp.core.ai.filter.*;
import ferp.core.ai.strategy.Strategy;
import ferp.core.ai.strategy.simple.SimpleDropping;
import ferp.core.card.Card;
import ferp.core.game.Game;
import ferp.core.game.Trick;
import ferp.core.log.Log;
import ferp.core.player.Hand;

/**
 * User: igorgok
 * Date: 11/20/11 10:17 AM
 *
 * Evaluator interface
 */
public abstract class Evaluator
{
  public static final int MISERE   = 0;
  public static final int PASSING  = 1;
  public static final int TRICKING = 2;

  public static final Evaluator[] evaluators = new Evaluator[] {new Misere(), new Passing(), new Tricking()};

  // evaluator for misere
  public static final class Misere extends Min
  {
    @Override
    boolean isBetter(short child, short parent, byte player, int declarer)
    {
      return player == declarer ? Score.get(child, player) < Score.get(parent, player) : Score.get(child, declarer) > Score.get(parent, declarer);
    }

    @Override
    int filter(int candidates, int[] hands, int player, int trick, Card.Suit trump, int round, int hand, int declarer, boolean open)
    {
      int filtered = candidates;

      if (declarer == 0)
      {
        if (player != declarer)
          filtered = FilterHumanPlaysMisere.apply(this, filtered, player, trick, round, hand, hands);
      }
      else
      {
        if (player == declarer)
          filtered = FilterAndroidPlaysMisere.apply(filtered, trick, round);
      }

      if (filtered == 0)
        filtered = candidates;

      // the call to super.filter() has to be made after the call to FilterAndroidPlaysMisere.apply()
      // in order to allow the FilterAndroidPlaysMisere to see the original candidates
      return super.filter(filtered, hands, player, trick, trump, round, hand, declarer, open);
    }

    @Override
    public int guess(int cards12, Card.Suit trump, boolean starting, int trash, int sets, int included)
    {
      return ((SimpleDropping)Strategy.SIMPLE.dropping()).misere(cards12, trump, starting, trash, sets, included);
    }
  }

  // evaluator for passing game
  private static final class Passing extends Min
  {
    @Override
    boolean isBetter(short child, short parent, byte player, int declarer)
    {
      return Score.get(child, player) < Score.get(parent, player);
    }

    @Override
    int filter(int candidates, int[] hands, int player, int trick, Card.Suit trump, int round, int hand, int declarer, boolean open)
    {
      int filtered = FilterMissingSuits.apply(candidates, hands, player, hand);

      return super.filter(filtered, hands, player, trick, trump, round, hand, declarer, open);
    }

    @Override
    public int guess(int cards12, Card.Suit trump, boolean starting, int trash, int sets, int included)
    {
      // no need to guess drop for passing
      return 0;
    }
  }

  // common basic evaluator functionality for pass and misere game
  private static abstract class Min extends Evaluator
  {
    @Override
    public int start(int candidates)
    {
      return Hand.first(candidates);
    }

    @Override
    public int next(int candidates, int current)
    {
      return Hand.next(candidates, current);
    }

    @Override
    public int doGetMoveCandidates(int[] hands, int player, int trick, Card.Suit trump, int round, int hand, int declarer, boolean open)
    {
      int in = hands[player], out = 0;
      Card.Suit suit = Trick.suit(trick);

      // trick has no defined suit yet - return the whole hand?
      if (suit == null)
        return in;

      // check the set matching the trick suit
      int set = Card.Set.get(in, suit);

      if (set != 0)
      {
        // include only:
        // 1. the highest card that is lower then trick's highest card
        // 2. all the cards higher then trick's highest card
        Card highest = Trick.highest(trick, trump);

        if (highest == null)
          // no highest card yet, add the whole set
          return Hand.only(in, suit);

        Card.Rank lower = Card.Set.lower(set, highest.rank);

        // add the card lower then the trick highest
        if (lower != null)
          out = Hand.add(0, suit, lower);

        // add higher cards
        for (Card.Rank higher = Card.Set.higher(set, highest.rank); higher != null; higher = Card.Set.higher(set, higher))
          out = Hand.add(out, suit, higher);
      }
      else
      {
        // no cards in trick's suit in this hand - for all other suits add their highest cards only
        for (Card.Suit s : Card.Suit.rising)
          if (s != suit)
          {
            Card.Rank highest = Card.Set.highest(Card.Set.get(in, s));

            if (highest != null)
              out = Hand.add(out, s, highest);
          }
      }

/*
      // if out hand is empty the player has no suits which are in game, return any card (only one!) as candidate
      if (out == 0)
        for (Card.Suit s : Card.Suit.rising)
        {
          set = Card.Set.get(in, s);

          if (set != 0)
            return Hand.add(0, s, Card.Set.highest(set));
        }
*/
      //gl.log("N#" + hand + ": [" + in + "], trick: [" + trick + "], suit: " + trick.suit() + ", optimized result: " + out);

      return out;
    }

    @Override
    int filter(int candidates, int[] hands, int player, int trick, Card.Suit trump, int round, int hand, int declarer, boolean open)
    {
      return FilterSameSuitConsecutiveCards.max.apply(candidates, ~Hand.merge(hands));
    }
  }

  private static final class Tricking extends Evaluator
  {
    @Override
    public int start(int candidates)
    {
      return Hand.last(candidates);
    }

    @Override
    public int next(int candidates, int current)
    {
      return Hand.previous(candidates, current);
    }

    @Override
    public int doGetMoveCandidates(int[] hands, int player, int trick, Card.Suit trump, int round, int hand, int declarer, boolean open)
    {
      int in = hands[player], out = 0;

      // no cards in the trick - return the whole hand, unless it's the first hand in the first round
      if (hand == 0)
      {
        // can not optimize yet for advanced round or declarer, in this case we return whole hand
        if (round > 2 || player == declarer)
          return in;

        // in the first few rounds we can use the "steel rules":
        // 1. move towards declarer with the lowest card
        // 2. move towards whister with highest card
        if (Game.next(player) == declarer)
        {
          for (Card.Suit s : Card.Suit.rising)
            if (open || s != trump)
            {
              Card.Rank lowest = Card.Set.lowest(Card.Set.get(in, s));

              if (lowest != null)
                out = Hand.add(out, s, lowest);
            }
        }
        else
        {
          for (Card.Suit s : Card.Suit.rising)
            if (open || s != trump)
            {
              Card.Rank highest = Card.Set.highest(Card.Set.get(in, s));

              if (highest != null)
                out = Hand.add(out, s, highest);
            }
        }

        return out;
      }

      // try to get the trick's suit
      Card.Suit suit = Trick.suit(trick);
      // try to return our set of cards in this suit
      int set = Card.Set.get(in, suit);

      if (set != 0)
      {
        if (hand == 2 && player == declarer)
        {
          // for the last hand return the lowest card, higher than the trick's highest card
          Card highest = Trick.highest(trick, trump);

          if (highest.suit == suit)
          {
            // add also the lowest card that is higher than the highest trick card
            Card.Rank higher = Card.Set.higher(set, highest.rank);

            if (higher != null)
              return Hand.add(0, suit, higher);
          }

          // always add the lowest card in the trick's suit
          return Hand.add(out, suit, Card.Set.lowest(set));
        }

        // no optimization yet, add the whole suit
        return Hand.only(in, suit);
      }
      else
      {
        // no cards in trick's suit, try trumps
        // declarer move optimization: can play the lowest trump (if has one) when sits on last hand or
        // the last hand has cards in trick suit
        if (trump != Card.Suit.NONE && (set = Hand.only(in, trump)) != 0)
        {
          //Resolver.debug(round == 2 && suit == Card.Suit.HEARTS && (Trick.get(trick, 1) & Card.MASK) == Deck.card(Card.Suit.DIAMONDS, Card.Rank.JACK).compressed());

          if (player == declarer && (hand == 2 || Hand.only(hands[Game.next(player)], suit) != 0))
          {
            int s = Card.Set.get(in, trump);
            // the highest card in the trick may be another trump card, so we have to beat it with the higher trump
            // should we have one otherwise play the lowest trump
            Card highest = Trick.highest(trick, trump);
            Card.Rank higher = highest.suit == trump ? Card.Set.higher(s, highest.rank) : null;

            //Game.log.debug(Log.TAG, "trick: " + Trick.dump(trick, deck) + ", highest: " + highest + ", higher: " + trump + higher);

            return Hand.add(0, trump, higher == null ? Card.Set.lowest(s) : higher);
          }

          return set;
        }

        // the hand has no trumps as well - add the lowest cards in all remaining suits
        for (Card.Suit s : Card.Suit.rising)
          if (s != suit)
          {
            Card.Rank lowest = Card.Set.lowest(Card.Set.get(in, s));

            if (lowest != null)
              out = Hand.add(out, s, lowest);
          }
      }

      //gl.log("X#" + hand + ": [" + in + "], trick: [" + trick + "], suit: " + trick.suit() + ", optimized result: " + out);

      return out;
    }

    @Override
    boolean isBetter(short child, short parent, byte player, int declarer)
    {
      return player == declarer ? Score.get(child, player) > Score.get(parent, player) : Score.get(child, declarer) < Score.get(parent, declarer);
    }

    @Override
    int filter(int candidates, int[] hands, int player, int trick, Card.Suit trump, int round, int hand, int declarer, boolean open)
    {
      int filtered = candidates;

      if (player == declarer && trump != Card.Suit.NONE && hand == 0 && round < 5)
        filtered = open ? FilterDeclarerFirstHandMoveOpen.apply(candidates, trump, declarer, hands) : FilterDeclarerFirstHandMove.apply(candidates, trump, declarer, hands);

      if (player != declarer)
      {
        if (hand == 0)
        {
          if (round < 4)
          {
              filtered = FilterSecondPlusKingTrick.apply(filtered, player, hands[player], declarer, hands[declarer]);
              filtered = FilterThirdQueenTrick.apply(filtered, hands[declarer]);
              filtered = FilterWhisterFirstHandMove.apply(filtered, hands[player], hands[declarer]);
          }
        }
        else
          if (round < 9 && ((hand == 1 && Game.previous(player) == declarer) || hand == 2))
          {
            if (open)
              filtered = FilterWhisterAfterDeclarerMoveOpen.apply(this, filtered, hands, player, trick, trump, round, hand, declarer);
            else
              filtered = FilterWhisterAfterDeclarerMove.apply(this, filtered, player, trick, trump, hand, declarer);
          }
      }

      if (hand != 0 && round < 8)
        filtered = FilterPlayerNonFirstHandMove.apply(filtered, player, trick, trump, hand);

      return FilterSameSuitConsecutiveCards.min.apply(filtered, ~Hand.merge(hands));
    }

    @Override
    public int guess(int cards12, Card.Suit trump, boolean starting, int trash, int sets, int included)
    {
      return ((SimpleDropping)Strategy.SIMPLE.dropping()).tricks(cards12, trump, starting, trash, sets, included);
    }
  }

  // guess drop
  public int guess(int player, int cards12, Card.Suit trump, int starter, int trash, int sets, int included)
  {
    Log.debug(Log.TAG, "guessing drop for player #" + player + ", full hand: [" + Hand.dump(cards12) + "], trump: " + trump + (included != 0 ? Hand.dump(included) : "") + ", trash: [" + Hand.dump(trash) + "]");

    return guess(cards12, trump, player == starter, trash, sets, included);
  }

  public int getSingleCandidate(int candidatesInHand, int[] hands, int player, int trick, Card.Suit trump, int round, int hand, int declarer, boolean open)
  {
    int candidates = filter(candidatesInHand, hands, player, trick, trump, round, hand, declarer, open);

    // filter may return more than one candidate
    return start(candidates);
  }

  abstract public int start(int candidates);
  abstract public int next(int candidates, int current);
  abstract public int doGetMoveCandidates(int[] hands, int player, int trick, Card.Suit trump, int round, int hand, int declarer, boolean open);
  abstract boolean isBetter(short child, short parent, byte player, int declarer);
  abstract int filter(int candidates, int[] hands, int player, int trick, Card.Suit trump, int round, int hand, int declarer, boolean open);
  abstract int guess(int cards12, Card.Suit trump, boolean starting, int trash, int sets, int included);

  int getMoveCandidates(int[] hands, int player, int trick, Card.Suit trump, int round, int hand, int declarer, boolean open)
  {
    return filter(doGetMoveCandidates(hands, player, trick, trump, round, hand, declarer, open),
                  hands, player, trick, trump, round, hand, declarer, open);
  }
}
